package movee.api.aspect;

import movee.domain.api.ExceptionResponse;
import movee.domain.exception.AppException;
import movee.domain.exception.AppStatusCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ClassUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import java.util.Arrays;
import java.util.Set;

/**
 *
 *
 * @author movee
 */
@ControllerAdvice(basePackages = {"movee.api.v1"})
@Slf4j
public class ApiExceptionHandler extends BasicApiExceptionHandler {

    @Autowired
    public ApiExceptionHandler(HttpServletRequest httpRequest) {
        super(httpRequest);
    }

    /**
     * 应用自定义异常
     * @param e e
     * @return response
     */
    @ExceptionHandler(AppException.class)
    public ResponseEntity<ExceptionResponse> handleAppException(AppException e) {

        AppStatusCode statusCode = e.getStatusCode();
        String message = e.getMessage();
        ExceptionResponse.Errors errors = new ExceptionResponse.Errors(Arrays.asList(ExceptionUtils.getStackFrames(e)),
                e.getDetail());

        ResponseEntity<ExceptionResponse> resp = buildExceptionResponseEntity(statusCode, message, errors);
        logException(e);
        return resp;
    }

    /**
     * valid参数检查异常
     * @param e e
     * @return response
     */
    @ExceptionHandler(BindException.class)
    public ResponseEntity<ExceptionResponse> handleBindException(BindException e) {

        AppStatusCode statusCode = AppStatusCode.INVALID_QUERY_PARAM;
        BindingResult br = e.getBindingResult();
        StringBuilder sb = new StringBuilder("参数错误：");
        br.getAllErrors().forEach(err -> sb.append(err.getDefaultMessage()).append(";"));
        String message = sb.toString();

        ExceptionResponse.Errors errors = new ExceptionResponse.Errors(Arrays.asList(ExceptionUtils.getStackFrames(e)),
                "");

        ResponseEntity<ExceptionResponse> resp = buildExceptionResponseEntity(statusCode, message, errors);
        logException(e);
        return resp;
    }

    /**
     * validated参数检查异常
     * @param e e
     * @return response
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public ResponseEntity<ExceptionResponse> handleConstraintViolationException(ConstraintViolationException e) {
        AppStatusCode statusCode = AppStatusCode.INVALID_QUERY_PARAM;
        ExceptionResponse.Errors errors = new ExceptionResponse.Errors(Arrays.asList(ExceptionUtils.getStackFrames(e)),
                "");

        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        StringBuilder sb = new StringBuilder("参数错误：");
        violations.forEach(v -> sb.append(v.getMessage()).append(";"));
        String message = sb.toString();

        ResponseEntity<ExceptionResponse> resp = buildExceptionResponseEntity(statusCode, message, errors);
        logException(e);
        return resp;
    }

    /**
     * mvc controller方法参数异常
     * @param e e
     * @return response
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ExceptionResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        AppStatusCode statusCode = AppStatusCode.INVALID_QUERY_PARAM;
        ExceptionResponse.Errors errors = new ExceptionResponse.Errors(Arrays.asList(ExceptionUtils.getStackFrames(e)),
                "");

        BindingResult br = e.getBindingResult();
        StringBuilder sb = new StringBuilder("参数错误：");
        br.getAllErrors().forEach(err -> sb.append(err.getDefaultMessage()).append(";"));
        String message = sb.toString();

        ResponseEntity<ExceptionResponse> resp = buildExceptionResponseEntity(statusCode, message, errors);
        logException(e);
        return resp;
    }

    /**
     * mvc controller参数类型不匹配异常
     * @param e e
     * @return response
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<ExceptionResponse> handleMethodArgumentTypeMismatchException(
            MethodArgumentTypeMismatchException e) {
        AppStatusCode statusCode = AppStatusCode.INVALID_QUERY_PARAM;
        ExceptionResponse.Errors errors = new ExceptionResponse.Errors(Arrays.asList(ExceptionUtils.getStackFrames(e)),
                "");

        String message = String.format("输入参数类型不匹配。参数名：%s, 传入类型: %s, 预期类型: %s",
                e.getName(),
                ClassUtils.getDescriptiveType(e.getValue()),
                e.getRequiredType() == null ? "" : e.getRequiredType().getTypeName());

        ResponseEntity<ExceptionResponse> resp = buildExceptionResponseEntity(statusCode, message, errors);
        logException(e);
        return resp;
    }

    /**
     * servlet请求参数异常
     * @param e e
     * @return response
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseEntity<ExceptionResponse> handleMissingServletRequestParameterException(
            MissingServletRequestParameterException e) {
        AppStatusCode statusCode = AppStatusCode.INVALID_QUERY_PARAM;
        ExceptionResponse.Errors errors = new ExceptionResponse.Errors(Arrays.asList(ExceptionUtils.getStackFrames(e)),
                "");

        String message = String.format("缺失必须的参数。参数名：%s, 参数类型: %s",
                e.getParameterName(), e.getParameterType());

        ResponseEntity<ExceptionResponse> resp = buildExceptionResponseEntity(statusCode, message, errors);
        logException(e);
        return resp;
    }

    /**
     * 请求缺少相应的请求头部
     * @param e e
     * @return response
     */
    @ExceptionHandler(MissingRequestHeaderException.class)
    public ResponseEntity<ExceptionResponse> handleMissingRequestHeaderException(MissingRequestHeaderException e) {
        AppStatusCode statusCode = AppStatusCode.INVALID_QUERY_PARAM;
        ExceptionResponse.Errors errors = new ExceptionResponse.Errors(Arrays.asList(ExceptionUtils.getStackFrames(e)),
                "");

        String message = String.format("缺失必须的请求头参数。请求头名字：%s, 请求头参数类型: %s",
                e.getHeaderName(), e.getParameter().getNestedParameterType().getSimpleName());

        ResponseEntity<ExceptionResponse> resp = buildExceptionResponseEntity(statusCode, message, errors);
        logException(e);
        return resp;
    }


    /**
     * 通用异常
     * @param t t
     * @return response
     */
    @ExceptionHandler(Throwable.class)
    public ResponseEntity<ExceptionResponse> handleGenericExcetion(Throwable t) {

        log.error("{}", ExceptionUtils.getStackTrace(t));
        AppStatusCode statusCode = AppStatusCode.INTERNAL_ERROR;
        String message = statusCode.getMessage();
        ExceptionResponse.Errors errors = new ExceptionResponse.Errors(Arrays.asList(ExceptionUtils.getStackFrames(t)),
                "");

        ResponseEntity<ExceptionResponse> resp = buildExceptionResponseEntity(statusCode, message, errors);
        logException(t);
        return resp;
    }

}
